#import "BWEvenweave.h"

@implementation BWEvenweave

#define FABRIC_WIDTH 10
#define FABRIC_SPACING 40

- (id) init
{
    if (self = [super init]) {
        flatGrid = YES;
    }

    return (self);

} // init


/*
A 1-in, N-out function, where N is one more (for alpha) than the
number of color components in the shading's color space. The input
value 0 corresponds to the color at the starting point of the
shading. The input value 1 corresponds to the color at the ending
point of the shading.
*/

static void evaluate (void *info, const float *in, float *out)
{
    BWEvenweave *ev = (BWEvenweave *)info;

    float thing;
    thing = in[0];

    if (thing > 0.5) {
        thing = 1.0 - thing;
    }

    thing = (thing + 2.0) / 3.0;

    out[0] = thing * ev->threadRed;
    out[1] = thing * ev->threadGreen;
    out[2] = thing * ev->threadBlue;
    out[3] = ev->threadAlpha;

} // evaluate


- (void) drawFlatGrid: (NSRect) rect
{
    [NSGraphicsContext saveGraphicsState]; {
        [NSBezierPath setDefaultLineWidth: [self fabricWidth]];

        // get the color specified in the model
        [[self threadColor] set];

        // this is how far to skip between the grid lines
        float stride;
        stride = [self fabricWidth] + [self fabricSpacing];

        // draw vertical lines
        // the +stride is to make sure that for wide lines, the extreme
        // sides get filled in
        float x;
        for (x = rect.origin.x; x <= rect.origin.x + rect.size.width + stride; 
             x += stride) {
            NSPoint start, end;
            start = NSMakePoint (x, rect.origin.y);
            end = NSMakePoint (x, rect.origin.y + rect.size.height);
            [NSBezierPath strokeLineFromPoint: start  toPoint: end];
        }
        
        // draw the horizontal lines
        float y;
        for (y = rect.origin.y; y <= rect.origin.y + rect.size.height + stride; 
             y += stride) {
            NSPoint start, end;
            start = NSMakePoint (rect.origin.x, y);
            end = NSMakePoint (rect.origin.x + rect.size.width, y);
            [NSBezierPath strokeLineFromPoint: start  toPoint: end];
        }

    } [NSGraphicsContext restoreGraphicsState];

} // drawFlatGrid


- (void) drawGrid: (NSRect) rect
{
    [NSGraphicsContext saveGraphicsState]; {
        float domain[2] = { 0.0, 1.0 };
        float range[8] = { 0.0, 1.0,
                           0.0, 1.0,
                           0.0, 1.0,
                           0.0, 1.0 };
        CGFunctionCallbacks callbacks = { 0, evaluate, NULL };
        

        CGFunctionRef shaderFunction;
        shaderFunction = CGFunctionCreate (self,
                                           1,
                                           domain,
                                           4,
                                           range,
                                           &callbacks);
                                           

        CGColorSpaceRef deviceRGB;
        deviceRGB = CGColorSpaceCreateDeviceRGB ();

        // draw two cell withds (plus grid line), skip one grid line, ...
        float twoWidth;
        twoWidth = [self fabricSpacing] * 2 + [self fabricWidth];
        

        CGPoint start;
        start.x = [self fabricWidth] / 2.0;
        start.y = 0.0;

        CGPoint end;
        end.x = [self fabricWidth] / 2.0;
        end.y = twoWidth;

        CGShadingRef horizBarShading;
        horizBarShading = CGShadingCreateAxial (deviceRGB,
                                                start,
                                                end,
                                                shaderFunction,
                                                NO,
                                                NO);
        start.x = 0.0;
        start.y = [self fabricWidth] / 2.0;

        end.x = twoWidth;
        end.y = [self fabricWidth] / 2.0;

        CGShadingRef vertBarShading;
        vertBarShading = CGShadingCreateAxial (deviceRGB,
                                               start,
                                               end,
                                               shaderFunction,
                                               NO,
                                               NO);
        
        CGContextRef context;
        context = [[NSGraphicsContext currentContext] graphicsPort];

        [NSBezierPath setDefaultLineWidth: edgeWidth];
        [[self edgeColor] set];

        // this is the amount of space for the 'skip one'
        float stride;
        stride = [self fabricWidth] + [self fabricSpacing];

        float vstride;
        vstride = twoWidth + [self fabricWidth];

        float bias = [self fabricWidth] / 2.0;

        // change the start for every other line
        BOOL jiggle = NO;
        
        CGRect cgrect = { { 0.0, 0.0 }, { [self fabricWidth], twoWidth } };

        float x, y, ystart;
        for (x = rect.origin.x - bias; x < rect.origin.x + rect.size.width; x += stride) {
            if (jiggle) {
                ystart = rect.origin.y;
            } else {
                // start off the top
                ystart = rect.origin.y - [self fabricSpacing] - [self fabricWidth];
            }
            jiggle = !jiggle;
            
            for (y = ystart + bias; y < rect.origin.y + rect.size.height;
                 y += vstride) {
                
                CGContextSaveGState (context); {

                    CGContextTranslateCTM (context, x, y);
                    CGContextClipToRect (context, cgrect);
                    CGContextDrawShading (context, horizBarShading);

                } CGContextRestoreGState (context);
            }
        }
        
        jiggle = YES;
        
        CGRect cgrect2 = { { 0.0, 0.0 }, { twoWidth, [self fabricWidth] } };

        float xstart;
        for (y = rect.origin.y - bias; y < rect.origin.y + rect.size.height; y += stride) {
            if (jiggle) {
                xstart = rect.origin.x;
            } else {
                // start off the left
                xstart = rect.origin.x - [self fabricSpacing] - [self fabricWidth];
            }
            jiggle = !jiggle;
            
            for (x = xstart + bias; x < rect.origin.x + rect.size.width;
                 x += vstride) {

                CGContextSaveGState (context); {

                    CGContextTranslateCTM (context, x, y);
                    CGContextClipToRect (context, cgrect2);
                    CGContextDrawShading (context, vertBarShading);

                } CGContextRestoreGState (context);
            }
        }

        // set the width of the pen to match the fabric width

        CGShadingRelease (horizBarShading);
        CGShadingRelease (vertBarShading);
        CGFunctionRelease (shaderFunction);
        
    } [NSGraphicsContext restoreGraphicsState];

} // drawGrid



// draw the lines to give the illusion of woven fabric

- (void) drawOverlapLines: (NSRect) rect
{
    [NSGraphicsContext saveGraphicsState]; {
        
        [NSBezierPath setDefaultLineWidth: edgeWidth];
        [[self edgeColor] set];

        // draw two cell withds (plus grid line), skip one grid line, ...
        float twoWidth;
        twoWidth = [self fabricSpacing] * 2 + [self fabricWidth];
        
        // this is the amount of space for the 'skip one'
        float stride;
        stride = [self fabricWidth] + [self fabricSpacing];

        float vstride;
        vstride = twoWidth + [self fabricWidth];

        float bias = [self fabricWidth] / 2.0;

        // change the start for every other line
        BOOL jiggle = NO;
        
        float x, y, ystart;
        for (x = rect.origin.x - bias; x < rect.origin.x + rect.size.width; x += stride) {
            if (jiggle) {
                ystart = rect.origin.y;
            } else {
                // start off the top
                ystart = rect.origin.y - [self fabricSpacing] - [self fabricWidth];
            }
            jiggle = !jiggle;
            
            for (y = ystart + bias; y < rect.origin.y + rect.size.height;
                 y += vstride) {
                NSPoint start, end;
                
                start = NSMakePoint (x, y);
                end = NSMakePoint (x, y + twoWidth);
                [NSBezierPath strokeLineFromPoint: start  toPoint: end];
                
                start = NSMakePoint (x + [self fabricWidth], y);
                end = NSMakePoint (x + [self fabricWidth], y + twoWidth);
                [NSBezierPath strokeLineFromPoint: start  toPoint: end];
            }
        }
        
        jiggle = YES;
        
        float xstart;
        for (y = rect.origin.y - bias; y < rect.origin.y + rect.size.height; y += stride) {
            if (jiggle) {
                xstart = rect.origin.x;
            } else {
                // start off the left
                xstart = rect.origin.x - [self fabricSpacing] - [self fabricWidth];
            }
            jiggle = !jiggle;
            
            for (x = xstart + bias; x < rect.origin.x + rect.size.width;
                 x += vstride) {
                NSPoint start, end;
                
                start = NSMakePoint (x, y);
                end = NSMakePoint (x + twoWidth, y);
                [NSBezierPath strokeLineFromPoint: start  toPoint: end];
                
                start = NSMakePoint (x, y + [self fabricWidth]);
                end = NSMakePoint (x + twoWidth, y + [self fabricWidth]);
                [NSBezierPath strokeLineFromPoint: start  toPoint: end];
            }
        }
    } [NSGraphicsContext restoreGraphicsState];

} // drawOverlapLines



- (void) drawInRect: (NSRect) rect
{
    if (flatGrid) {
        [self drawFlatGrid: rect];
    } else {
        [self drawGrid: rect];
    }
    [self drawOverlapLines: rect];

} // drawInRect


- (NSRect) rectForSquare: (BWGrid_Square) square
{
    NSRect wholeRect;

    float rx, ry;
    rx = (square.x * [self fabricSpacing]) + ([self fabricWidth] * square.x) 
          + [self fabricWidth] / 2.0;
    ry = (square.y * [self fabricSpacing]) + ([self fabricWidth] * square.y) 
          + [self fabricWidth] / 2.0;

    wholeRect.origin.x = rx;
    wholeRect.origin.y = ry;
    wholeRect.size.width = [self fabricSpacing];
    wholeRect.size.height = [self fabricSpacing];

    return (wholeRect);
    
} // rectForSquare


- (NSRect) rectForSquare: (BWGrid_Square) square
                    part: (BWGrid_SquarePart) part
{
    NSRect wholeRect = [self rectForSquare: square];

    // now subdivide and look
    NSSize size;
    size.width = wholeRect.size.width / 3;
    size.height = wholeRect.size.height / 3;

    NSPoint origin;
    origin = wholeRect.origin;

    NSRect rect;
    rect.origin = origin;
    rect.size = size;

    switch (part) {
    case BWGrid_NW:
        // it's done
        break;
    case BWGrid_N:
        rect.origin.x += size.width;
        break;
    case BWGrid_NE:
        rect.origin.x += size.width * 2;
        break;

    case BWGrid_W:
        rect.origin.y += size.height;
        break;
    case BWGrid_Center:
        rect.origin.y += size.height;
        rect.origin.x += size.width;
        break;
    case BWGrid_E:
        rect.origin.y += size.height;
        rect.origin.x += size.width * 2;
        break;

    case BWGrid_SW:
        rect.origin.y += size.height * 2;
        break;
    case BWGrid_S:
        rect.origin.y += size.height * 2;
        rect.origin.x += size.width;
        break;
    case BWGrid_SE:
        rect.origin.y += size.height * 2;
        rect.origin.x += size.width * 2;
        break;

    case BWGrid_WholeSquare:
        rect = wholeRect;
        break;

    default:
        NSLog (@"squareRect: unexpected case: %d", part);
        abort ();
    }

    return (rect);

} // rectForSquare


- (BWGrid_SquarePart) mergeHorizontalPart: (BWGrid_SquarePart) horiz
                          withVerticalPart: (BWGrid_SquarePart) vert
{
    BWGrid_SquarePart part;

    if (horiz == BWGrid_W) {
        if (vert == BWGrid_N) {
            part = BWGrid_NW;
        } else if (vert == BWGrid_S) {
            part = BWGrid_SW;
        } else {
            part = BWGrid_W;
        }

    } else if (horiz == BWGrid_E) {
        if (vert == BWGrid_N) {
            part = BWGrid_NE;
        } else if (vert == BWGrid_S) {
            part = BWGrid_SE;
        } else {
            part = BWGrid_E;
        }

    } else {
        part = vert;
    }

    return (part);

} // mergeHorizontalPart


- (void) mapPoint: (NSPoint) point
         toSquare: (BWGrid_Square *) square
             part: (BWGrid_SquarePart *) part
  forceCenterOnly: (BOOL) forceCenterOnly
{
    // avoid having to test for part != NULL everywhere
    BWGrid_SquarePart nullEater;
    if (part == NULL) {
        part = &nullEater;
    }

    float width = [self fabricWidth];
    float spacing = [self fabricSpacing];
    float cellSize = width + spacing;
    square->x = point.x / cellSize;
    square->y = point.y / cellSize;

    *part = BWGrid_Center;

    if (!forceCenterOnly) {
        NSPoint origin;
        origin.x = square->x * cellSize;
        origin.y = square->y * cellSize;

        float third = cellSize / 3.0;

        BWGrid_SquarePart horiz, vert;
        
        if (point.x < origin.x + third) {
            horiz = BWGrid_W;
        } else if (point.x < origin.x + third * 2.0) {
            horiz = BWGrid_Center;
        } else {
            horiz = BWGrid_E;
        }
        
        if (point.y < origin.y + third) {
            vert = BWGrid_N;
        } else if (point.y < origin.y + third * 2.0) {
            vert = BWGrid_Center;
        } else {
            vert = BWGrid_S;
        }

        *part = [self mergeHorizontalPart: horiz
                      withVerticalPart: vert];
    }

    if (square->x < 0 || square->y < 0) {
        *part = BWGrid_InvalidSquare;
    }

} // mapPoint toSquare

- (void) setThreadColor: (NSColor *) newColor
{
    [super setThreadColor: newColor];

    // get red / gree / blue / alpha for the fancy grid drawing
    
    NSColor *rgbColor;

    // convert to RGB in case it's not in rgb space already
    rgbColor = [newColor colorUsingColorSpaceName: 
                             NSCalibratedRGBColorSpace];

    [rgbColor getRed: &threadRed
              green: &threadGreen
              blue: &threadBlue
              alpha: &threadAlpha];

} // setThreadColor


@end // BWEvenweave

